package connect

import (
	"encoding/json"
	"litetalk/config"
	"litetalk/proto"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
	"github.com/sirupsen/logrus"
)

func GoWS() {
	server := &http.Server{
		Addr:    config.Conf.WS.Bind,
		Handler: InitWebSocket(),
	}

	go func() {
		logrus.Infof("WS listen on [%s]", server.Addr)
		if err := server.ListenAndServe(); err != nil {
			logrus.Errorf("connect listen ws err:%s", err)
		}
	}()
}

func InitWebSocket() *http.ServeMux {
	h := http.NewServeMux()
	h.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		serverWs(&DefaultServer, w, r)
	})
	return h
}

func serverWs(s *Server, w http.ResponseWriter, r *http.Request) {
	logrus.Infof("handle server websocket")
	var up = websocket.Upgrader{
		ReadBufferSize:  config.Conf.Server.ReadBufferSize,
		WriteBufferSize: config.Conf.Server.WriteBufferSize,
	}
	up.CheckOrigin = func(r *http.Request) bool { return true }

	conn, err := up.Upgrade(w, r, nil)
	if err != nil {
		logrus.Errorf("connect serverWs err:%s", err.Error())
		return
	}
	ch := NewChannel(config.Conf.Server.BroadcastSize)
	ch.connWS = conn
	go writeWs(s, ch)
	go readWs(s, ch)
}

func writeWs(s *Server, ch *Channel) {
	defer func() {
		ch.connWS.Close()
	}()

	conn := ch.connWS
	var (
		m  *proto.Msg
		ok bool
	)

	ticker := time.NewTicker(config.Conf.Server.PingPeriod)

	for {
		select {
		case <-ticker.C:
			conn.SetWriteDeadline(time.Now().Add(config.Conf.Server.WriteWait))
			if err := conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
				return
			}
			continue
		case m, ok = <-ch.ch:
		}
		if !ok {
			conn.WriteMessage(websocket.CloseMessage, []byte{})
			return
		}
		if err := conn.SetWriteDeadline(time.Now().Add(config.Conf.Server.WriteWait)); err != nil {
			logrus.Warn("SetWriteDeadline not ok")
			conn.WriteMessage(websocket.CloseMessage, []byte{})
			return
		}
		w, err := conn.NextWriter(websocket.BinaryMessage)
		if err != nil {
			logrus.Warn(" ch.conn.NextWriter err :%s  ", err)
			return
		}
		m.Pack(w)
		if err := w.Close(); err != nil {
			return
		}
	}
}

func readWs(s *Server, ch *Channel) {
	defer func() {
		logicRpc.DisConnect(&proto.DisConnect{
			AuthToken: ch.authToken,
		}, &proto.DisConnectReply{})
		ch.Close()
		s.Bucket(ch.userId).DeleteChannel(ch)
		logrus.Infof("user offline id:%d", ch.userId)
	}()

	conn := ch.connWS

	conn.SetReadLimit(config.Conf.Server.MaxMessageSize)
	conn.SetReadDeadline(time.Now().Add(config.Conf.Server.PongWait))
	conn.SetPongHandler(func(appData string) error {
		conn.SetReadDeadline(time.Now().Add(config.Conf.Server.PongWait))
		return nil
	})

	for {
		_, reader, err := conn.NextReader()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				logrus.Errorf(" 1 %v", err)
			} else {
				logrus.Errorf(" 2 %v", err)
			}
			break
		}
		msg := &proto.Msg{}
		msg.Unpack(reader)
		if msg.Op != proto.OpBuildConn {
			continue
		}
		connReq := &proto.Connect{}
		if err := json.Unmarshal(msg.Msg, connReq); err != nil {
			logrus.Errorf("Unmarshal connect error : '%s' raw:'%s'", err, msg.Msg)
			continue
		}

		connRep := &proto.ConnectReply{}
		if err := logicRpc.Connect(connReq, connRep); err != nil {
			logrus.Errorf("Connect error: ", err)
		} else {
			s.Bucket(connRep.UserId).Put(connRep.UserId, ch)
			logrus.Infof("auth:%s Connect ok.", connReq.AuthToken)
			break
		}
	}

	for {
		_, reader, err := conn.NextReader()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				logrus.Errorf(" 1 %v", err)
			} else {
				logrus.Errorf(" 2 %v", err)
			}
			break
		}
		msg := &proto.Msg{}
		msg.Unpack(reader)

		s.Operator(msg, ch)
	}
}
